using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using OhioState.AssetManaged;

namespace OhioState.Graphics.Proxy
{
    /// <summary>
    /// This is a wrapper for ShaderProgramGLSL in order to handle events
    /// Note: The same property names as in ShaderProgramGLSL are used
    /// </summary>
    [Serializable()]
    public class ShaderProgram : SharedAsset, IShaderProgram
    {
        /// <summary>
        /// Define in built shader programs that would be used as default shader program
        /// </summary>
        public static ShaderProgram SolidColor = new ShaderProgram("InternalSolidColor", VertexShader.SolidColor, FragmentShader.SolidColor);
        /// <summary>
        /// Modulate Texture shader program that uses the VertexWithTexture and ModulateTexture vertex and fragment codes respectively
        /// </summary>
        public static ShaderProgram ModulateTex = new ShaderProgram("ModulateTexture", VertexShader.VertexWithTexture, FragmentShader.ModulateTexture);

        static ShaderProgram()
        {
            AssetManager.Instance.Add(SolidColor);
            AssetManager.Instance.Add(ModulateTex);
        }
        
        #region Public Properties
        private bool needsLinked;
        
        /// <summary>
        /// Get Accessor to check if shader needs to be linked
        /// </summary>
        public bool NeedsLinked
        {
            get { return this.needsLinked; }
        }
        [NonSerialized()]
        private String linkInfo;
        /// <summary>
        /// Gets the Link Info
        /// </summary>
        public String LinkInfo
        {
            get { return this.linkInfo; }
        }

        private List<VertexShader> vertexList;
        /// <summary>
        /// Gets the current vertex program list
        /// </summary>
        public List<VertexShader> VertexList
        {
            get { return this.vertexList; }
        }

        private List<FragmentShader> fragmentList;
        /// <summary>
        /// Gets the current fragment program list
        /// </summary>
        public List<FragmentShader> FragmentList
        {
            get { return this.fragmentList; }
        }
        #endregion

        
        
        /// <summary>
        /// Dictionary for Uniform Variable along with variable name  
        /// </summary>
        private Dictionary<String, UniformVariableInfo> uniformVariables;


        
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name">name</param>
        
        public ShaderProgram(String name)
            : this(name, new List<VertexShader>(1), new List<FragmentShader>(1))
        {
        }
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name">shader program name</param>
        /// <param name="vertexShader">vertex program</param>
        /// <param name="fragmentShader">fragment program</param>
        public ShaderProgram(String name, VertexShader vertexShader, FragmentShader fragmentShader)
            : this(name, new List<VertexShader>(1), new List<FragmentShader>(1))
        {
            this.VertexListAdd(vertexShader);
            this.FragmentListAdd(fragmentShader);
        }
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name">shader program</param>
        /// <param name="vertexShaders">vertex program</param>
        /// <param name="fragmentShaders">fragment program</param>
        public ShaderProgram(String name, IList<VertexShader> vertexShaders,
            IList<FragmentShader> fragmentShaders) : base(name)
        {
            this.vertexList = new List<VertexShader>();
            foreach (VertexShader shader in vertexShaders)
                this.VertexListAdd(shader);
            this.fragmentList = new List<FragmentShader>();
            foreach (FragmentShader shader in fragmentShaders)
                this.FragmentListAdd(shader);

            this.uniformVariables = new Dictionary<String, UniformVariableInfo>();
            this.needsLinked = true;
        }

        
        
        /// <summary>
        /// Copy Constructor
        /// </summary>
        /// <param name="program">shader program</param>
        public ShaderProgram(ShaderProgram program)
        {
            this.Name = String.Concat(program.Name, " copy");
            this.vertexList = new List<VertexShader>();
            this.fragmentList = new List<FragmentShader>();
            this.uniformVariables = new Dictionary<String, UniformVariableInfo>();
            foreach (VertexShader shader in program.VertexList)
            {
                this.VertexListAdd(shader);
            }

            foreach (FragmentShader shader in program.FragmentList)
            {
                this.FragmentListAdd(shader);
            }

            foreach (String key in program.GetUniformVariables().Keys)
            {
                this.uniformVariables.Add(key, program.GetUniformVariables()[key]);
            }

            this.Name = program.Name;
            this.linkInfo = program.LinkInfo;
            this.needsLinked = program.NeedsLinked;
        }


        
        
        /// <summary>
        /// Handles selection changed event for asset manager control in charge of selecting
        ///  vertex shaders.
        /// </summary>
        /// <param name="items">list of selected vertex programs</param>
        public void vertexListChanged(IList<IAssetManaged> items)
        {
            VertexListClear();
            if (items.Count > 0)
            {
                foreach (IAssetManaged obj in items)
                {
                    if (obj is VertexShader)
                    {
                        VertexListAdd(obj as VertexShader);
                    }
                }
            }
            this.FireUpdateChangedEvent(this);
        }

        
        
        /// <summary>
        /// Handles selection changed event for asset manager control in charge of selecting
        ///  fragment shaders.
        /// </summary>
        /// <param name="items">List of selected fragment programs</param>
        public void fragmentListChanged(IList<IAssetManaged> items)
        {
            FragmentListClear();
            if (items.Count > 0)
            {
                foreach (IAssetManaged obj in items)
                {
                    if (obj is FragmentShader)
                    {
                        FragmentListAdd(obj as FragmentShader);
                    }
                }
            }
            this.FireUpdateChangedEvent(this);
        }
        /// <summary>
        /// Gets the uniform variables used
        /// </summary>
        /// <returns>uniform variables</returns>
        public Dictionary<String, UniformVariableInfo> GetUniformVariables()
        {
            return this.uniformVariables;
        }

        public void Invalidate()
        {
            this.FireUpdateChangedEvent(this);
        }
        /// <summary>
        /// Clone this shader program
        /// </summary>
        /// <returns>cloned shader program</returns>
        public ShaderProgram Clone()
        {
            return new ShaderProgram(this);
        }

        /// <summary>
        /// Overrides shared object ToString method
        /// </summary>
        /// <returns>A string representing this object's name</returns>
        public override string ToString()
        {
            return this.Name;
        }

        #region List accessors
        void ShaderChanged(IAssetManaged asset)
        {
            FireUpdateChangedEvent(this);
        }
        
        
        /// <summary>
        /// VertexList accessors
        /// </summary>
        /// <param name="shader">vertex program</param>
        protected void VertexListAdd(VertexShader shader)
        {
            if (!vertexList.Contains(shader))
            {
                vertexList.Add(shader);
                shader.AddChangedEventHandler(ShaderChanged);
            }
        }
        /// <summary>
        /// FragmentList accessors
        /// </summary>
        /// <param name="shader">fragment program</param>
        protected void FragmentListAdd(FragmentShader shader)
        {
            if (!fragmentList.Contains(shader))
            {
                fragmentList.Add(shader);
                shader.AddChangedEventHandler(ShaderChanged);
            }
        }
        /// <summary>
        /// Clears the current vertex shader list selection
        /// </summary>
        protected void VertexListClear()
        {
            foreach (VertexShader shader in vertexList)
            {
                shader.RemoveChangedEventHandler(ShaderChanged);
            }
            vertexList.Clear();
        }
        /// <summary>
        /// Clears the current fragment shader list selection
        /// </summary>
        protected void FragmentListClear()
        {
            foreach (FragmentShader shader in fragmentList)
            {
                shader.RemoveChangedEventHandler(ShaderChanged);
            }
            fragmentList.Clear();
        }
        /// <summary>
        /// Count the # Vertex Programs in the list
        /// </summary>
        /// <returns>count value</returns>
        public int VertexListCount()
        {
            return vertexList.Count;
        }
        /// <summary>
        /// Count the # Fragment Programs in the list
        /// </summary>
        /// <returns>count value</returns>
        public int FragmentListCount()
        {
            return fragmentList.Count;
        }
        /// <summary>
        /// Gets the vertex shader given the index
        /// </summary>
        /// <param name="index">index</param>
        /// <returns>vertex program</returns>
        public VertexShader GetVertexShader(int index)
        {
            return vertexList[index];
        }
        /// <summary>
        /// Gets the fragment shader given the index
        /// </summary>
        /// <param name="index">index</param>
        /// <returns>fragment program</returns>
        public FragmentShader GetFragmentShader(int index)
        {
            return fragmentList[index];
        }

        /// <summary>
        /// Set the Link information
        /// </summary>
        /// <param name="info">link information</param>
        public void SetLinkInfo(String info)
        {
            this.linkInfo = info;
        }

        /// <summary>
        /// Set whether the shader need to be linked
        /// </summary>
        /// <param name="need">bool for link needed or not</param>
        public void SetNeedsLinked(bool need)
        {
            this.needsLinked = need;
        }
        #endregion


        #region File operations

        
        
        /// <summary>
        /// LoadFromFile - static function that loads a shader program from a file
        ///  using binary deserialization.
        /// </summary>
        /// <param name="filename">file to be loaded from</param>
        /// <returns>shader program</returns>
        public static ShaderProgram LoadFromFile(String filename)
        {
            Stream stream = File.Open(filename, FileMode.Open);
            BinaryFormatter formatter = new BinaryFormatter();
            ShaderProgram obj;
            try
            {
                obj = (ShaderProgram)formatter.Deserialize(stream);
            }
            catch
            {
                obj = null;
            }
            stream.Close();
            return obj;
        }

        
        
        /// <summary>
        /// SaveToFile - function that saves a shader program to a file
        ///  using binary serialization.  This is not static and is simply called on the
        ///  object that needs to be saved
        /// </summary>
        /// <param name="filename">filename for saving</param>
        public void SaveToFile(String filename)
        {
            //
            // Make a clone to save.  The marshalling done by remoting causes problems with 
            //  serialization.
            //
            ShaderProgram obj = this.Clone();
            Stream stream = File.Open(filename, FileMode.Create);
            BinaryFormatter formatter = new BinaryFormatter();

            formatter.Serialize(stream, obj);
            stream.Close();
        }
        /// <summary>
        /// Implementation of SharedAsset ReaquireAssets method
        /// </summary>
        public override void ReaquireAssets()
        {
            //
            // Use the Properties to ensure the old event handlers are removed and new
            //    ones are added. Particularly if we are setting an item to itself.
            //
            List<FragmentShader> fragShaders = new List<FragmentShader>(fragmentList.Count);
            foreach (FragmentShader shader in fragmentList)
            {
                IAssetManaged item = AssetManager.Instance.Find(shader.GetType(), shader.Name);
                fragShaders.Add(item as FragmentShader);
            }
            this.fragmentList = fragShaders;
            List<VertexShader> vertShaders = new List<VertexShader>(vertexList.Count);
            foreach (VertexShader shader in vertexList)
            {
                IAssetManaged item = AssetManager.Instance.Find(shader.GetType(), shader.Name);
                vertShaders.Add(item as VertexShader);
            }
            this.vertexList = vertShaders;
        }
        [OnDeserialized]
        private void OnDeserialized(StreamingContext context)
        {
        }
        #endregion

        /// <summary>
        /// Get the number of samplers
        /// </summary>
        public void GetNumSamplers()
        {
            // Check to see if the program has been created.
        }

        #region IShader Members

        /// <summary>
        /// Implementation of IShader Build method
        /// </summary>
        /// <returns>bool for built or not</returns>
        public bool Build()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Implementation of IShader MakeActive method
        /// </summary>
        /// <param name="panel">render panel</param>
        public void MakeActive(IRenderPanel panel)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Implementation of IShader Deactivate method
        /// </summary>
        public void Deactivate()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Get the Guid
        /// </summary>
        public uint Guid
        {
            get { throw new Exception("The method or operation is not implemented."); }
        }

        #endregion
    }
}
